<?php

use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Date;
use Yaf\Request\Http;

class Request extends Http
{
    //静态变量保存全局实例
    private static ?Request $instance = null;


    //静态方法，单例统一访问入口
    public static function instance(): ?Request
    {
        if (is_null ( self::$instance ) || isset ( self::$instance )) {
            self::$instance = new self ();
        }
        return self::$instance;
    }
    /**
     * 获取所有请求参数
     * @return array
     */
    public static function all(): array
    {
        if (!self::instance()->isGet()) {
            if (self::instance()->server("CONTENT_TYPE")) {
                if (str_contains(self::instance()->server("CONTENT_TYPE"), "json")) {
                    $data = file_get_contents('php://input');
                    $data = json_decode($data, true);
                    if (json_last_error() !== JSON_ERROR_NONE) {
                        return [];
                    }
                }else if (str_contains(self::instance()->server("CONTENT_TYPE"), "xml")) {
                    $data = self::instance()->xmlToArray(file_get_contents('php://input'));
                }else{
                    if (!self::instance()->isPost()) {
                        $data = self::instance()->getRequest();
                    }else {
                        $data = self::instance()->getPost();
                    }
                }
                return array_merge($data,self::instance()->getQuery());
            }else {
                return self::instance()->getQuery();
            }
        }else {
            return self::instance()->getQuery();
        }
    }

    /**
     * 获取请求参数
     * @param string|null $key
     * @param mixed|null $default
     * @return mixed
     */
    public static function input(string $key = null, mixed $default = null): mixed
    {
        if (!self::instance()->isGet()) {
            if (self::instance()->server("CONTENT_TYPE")) {
                if (str_contains(self::instance()->server("CONTENT_TYPE"), "json")) {
                    $data = file_get_contents('php://input');
                    $data = json_decode($data, true);
                    if (json_last_error() !== JSON_ERROR_NONE) {
                        return NULL;
                    }
                }elseif (str_contains(self::instance()->server("CONTENT_TYPE"), "xml")) {
                    $data = self::instance()->xmlToArray(file_get_contents('php://input'));
                }else {
                    return self::instance()->getPost($key,$default);
                }
            }
            if ($default && !isset($data[$key])) {
                return $default;
            } else {
                if (isset($data)) {
                    array_merge($data, self::instance()->getQuery());
                    return $data[$key] ?? NULL;
                }else{
                    return NULL;
                }
            }
        }else {
            return self::instance()->get($key, $default);
        }
    }

    /**
     * 获取post请求参数
     * @param string|null $key
     * @param mixed|null $default
     * @return mixed
     */
    public function post(string $key = null, mixed $default = null): mixed
    {
        return $this->getPost($key, $default);
    }

    /**
     * Retrieve input as a boolean value.
     *
     * Returns true when value is "1", "true", "on", and "yes". Otherwise, returns false.
     *
     * @param string|null $key
     * @param bool $default
     * @return bool
     */
    public function boolean(string $key = null, bool $default = false): bool
    {
        return filter_var(self::input($key, $default), FILTER_VALIDATE_BOOLEAN);
    }

    /**
     * Retrieve input from the request as a Carbon instance.
     *
     * @param string $key
     * @param string|null $format
     * @param string|null $tz
     * @return Carbon|null
     */
    public function date(string $key, string $format = null, string $tz = null): ?Carbon
    {
        if (is_null($format)) {
            return Date::parse(self::input($key), $tz);
        }
        return Date::createFromFormat($format, self::input($key), $tz);
    }

    /**
     * Get a subset containing the provided keys with values from the input data.
     *
     * @param array|mixed $keys
     * @return array
     */
    public function only(mixed $keys): array
    {
        $results = [];
        $input = self::all();
        $placeholder = new stdClass;
        foreach (is_array($keys) ? $keys : func_get_args() as $key) {
            $value = data_get($input, $key, $placeholder);
            if ($value !== $placeholder) {
                Arr::set($results, $key, $value);
            }
        }
        return $results;
    }

    /**
     * Get all of the input except for a specified array of items.
     *
     * @param array|mixed $keys
     * @return array
     */
    public function except(mixed $keys): array
    {
        $keys = is_array($keys) ? $keys : func_get_args();
        $results = self::all();
        Arr::forget($results, $keys);
        return $results;
    }

    /**
     * Check that the given file is a valid file instance.
     *
     * @param mixed $file
     * @return bool
     */
    protected function isValidFile(mixed $file): bool
    {
        return $file instanceof SplFileInfo && $file->getPath() !== '';
    }

    /**
     * Determine if the uploaded data contains a file.
     *
     * @param string $key
     * @return bool
     */
    public function hasFile(string $key): bool
    {
        if (!is_array($files = $this->getFiles($key))) {
            $files = [$files];
        }
        foreach ($files as $file) {
            if ($this->isValidFile($file)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Determine if the request contains a given input item key.
     *
     * @param array|string $key
     * @return bool
     */
    public function exists(array|string $key): bool
    {
        return $this->has($key);
    }

    /**
     * Determine if the request contains a given input item key.
     *
     * @param array|string $key
     * @return bool
     */
    public function has(array|string $key): bool
    {
        $keys = is_array($key) ? $key : func_get_args();
        $input = self::all();
        foreach ($keys as $value) {
            if (!Arr::has($input, $value)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Determine if the request contains any of the given inputs.
     *
     * @param array|string $keys
     * @return bool
     */
    public function hasAny(array|string $keys): bool
    {
        $keys = is_array($keys) ? $keys : func_get_args();
        $input = self::all();
        return Arr::hasAny($input, $keys);
    }

    /**
     * Retrieve a server variable from the request.
     *
     * @param string|null $key
     * @param array|string|null $default
     * @return string|array|null
     */
    public function server(string $key = null, array|string $default = null): array|string|null
    {
        return $this->getServer($key, $default);
    }

    /**
     * Determine if the given input key is an empty string for "has".
     *
     * @param string $key
     * @return bool
     */
    protected function isEmptyString(string $key): bool
    {
        $value = self::input($key);
        return !is_bool($value) && !is_array($value) && trim((string)$value) === '';
    }

    /**
     * 解析XML内容到数组
     * @param string $xml
     * @return array
     */
    public function xml2arr(string $xml): array
    {
        if (PHP_VERSION_ID < 80000) {
            $backup = libxml_disable_entity_loader(true);
            $data = (array)simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
            libxml_disable_entity_loader($backup);
        } else {
            $data = (array)simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        }
        return json_decode(json_encode($data), true);
    }

    /**
     * 解析XML文本内容
     * @param string $xml
     * @return array|false
     */
    public function xmlToArray(string $xml): false|array
    {
        $state = xml_parse($parser = xml_parser_create(), $xml, true);
        return xml_parser_free($parser) && $state ? $this->xml2arr($xml) : false;
    }
}

